package gui.inventory;

import hit.core.ProductContainer;
import hit.core.ProductGroup;
import hit.core.StorageUnit;
import hit.core.TreeNotification;

import java.util.Observable;
import java.util.Observer;

public class TreeObserver implements Observer {
	private IInventoryView view;
	private ProductContainerData data_root;
	
	/**
	 * @param view the view this Observer belongs to
	 * */
	public TreeObserver(IInventoryView view, ProductContainerData data_root) {
		this.view = view;
		this.data_root = data_root;
	}
	
	@Override
	public void update(Observable o, Object response) {				
		TreeNotification notification = (TreeNotification) response;
		
		if (!IsValidParentContainer(notification))
			view.displayErrorMessage("Invalid parent Container");

		switch(notification.GetNotificationType()) {
		case 
		NODE_ADDED:
			HandleNodeAdded(notification);
			break;
		case NODE_REMOVED:
			HandleNodeRemoved(notification);
			break;
		case NODE_EDITED:
			HandleNodeEdited(notification);
			break;
		} 
		
	}
	
	/**
	 * Check if the parent container was valid
	 * @param notification the TreeNotification detailing the added node 
	 * */
	private boolean IsValidParentContainer(TreeNotification notification) {
		ProductContainer modifiedContainer = 
			(ProductContainer) notification.GetModifiedContainer();
		ProductContainerData data_parentNode = FindParent(modifiedContainer);

		if (data_parentNode == null) {
			return false;
		}	
		return true;
	}
	
	/**
	 * Handle the case that a node is added to the tree
	 * @param notification the TreeNotification detailing the added node
	 * */
	private void HandleNodeAdded(TreeNotification notification) {
		//get the parent nodes of the added node from the tree
		ProductContainer modifiedContainer = 
			(ProductContainer) notification.GetModifiedContainer();
		ProductContainerData data_parentNode = FindParent(modifiedContainer);
		
		//set up the new data node based on the node added to the model
		ProductContainerData data_newNode = new ProductContainerData();
		data_newNode.setName(modifiedContainer.GetName());
		data_newNode.setTag(modifiedContainer);
		
		//add the child to the data node's children
		data_parentNode.addChild(data_newNode);
		
		//add the child to the right index in the view
		int count = 0;
		if (data_parentNode.getChildCount() != 0) {
			while (count < data_parentNode.getChildCount() &&
					data_parentNode.getChild(count).getName().
								compareTo(data_newNode.getName()) < 0) {
				count++;
			}
		}
		view.insertProductContainer(data_parentNode, data_newNode, count);
		view.selectProductContainer(data_newNode);
		
		//make the new node the selected node in the tree
		view.setContextUnit(modifiedContainer.GetRootContainer().GetName());
		if (modifiedContainer instanceof ProductGroup)
		{
			view.setContextGroup(modifiedContainer.GetName());
			view.setContextSupply((
					(ProductGroup) modifiedContainer).GetThreeMonth().toString());
		}
		else
		{
			view.setContextGroup("");
			view.setContextSupply("");
		}
	}
	
	/**
	 * Handle the case that a node was removed from the tree
	 * @param notification the TreeNotification detailing the changes in the tree
	 * */
	private void HandleNodeRemoved(TreeNotification notification) {
		//get the parent nodes of the added node from the tree
		ProductContainer modifiedContainer = 
			(ProductContainer) notification.GetModifiedContainer();
		ProductContainerData data_parentNode = FindParent(modifiedContainer);
		
		//find the ProductContainerData that needs to be removed
		ProductContainerData data_toDelete = null;
		for (int i = 0; i < data_parentNode.getChildCount(); i++) {
			
			if (data_parentNode.getChild(i).getTag() == modifiedContainer) {
				data_toDelete = data_parentNode.getChild(i);
				break;
			}
		}
		
		//remove the data node
		data_parentNode.deleteChild(data_toDelete);
		view.deleteProductContainer(data_toDelete);
		
		//set the view to select the root node
		view.selectProductContainer(data_root);
		view.setContextUnit("");
		view.setContextSupply("");
		view.setContextGroup("");
	}
	
	/**
	 * Handle the case that a node in the tree was edited
	 * @param notification the TreeNotification detailing the changes in the tree
	 * */
	private void HandleNodeEdited(TreeNotification notification) {
		//get the parent nodes of the added node from the tree
		ProductContainer modifiedContainer = 
			(ProductContainer) notification.GetModifiedContainer();
		ProductContainerData data_parentNode = FindParent(modifiedContainer);
		
		//find which data node needs to be edited
		ProductContainerData data_toEdit = null;
		ProductContainer original = notification.GetOriginalContainer();
		for (int i = 0; i < data_parentNode.getChildCount(); i++) {
			
			if (data_parentNode.getChild(i).getTag() == original) {
				data_toEdit = data_parentNode.getChild(i);
				break;
			}
		}
		
		//edit the node at the right index
		int count = 0;
		boolean pastOriginal = false;
		if (data_parentNode.getChildCount() != 0) {
			while (count < data_parentNode.getChildCount() &&
					data_parentNode.getChild(count).getName().
								compareTo(modifiedContainer.GetName()) < 0) {
				if (data_parentNode.getChild(count).getName().equals(data_toEdit.getName()))
					pastOriginal = true;
				count++;
			}
		}			
		if (pastOriginal) {//the old node is still in the tree, so we need to skip it
			count--;
		}
		view.renameProductContainer(data_toEdit, modifiedContainer.GetName(), count);
		view.selectProductContainer(data_toEdit);
		
		//set the text fields in the view to the right values
		view.setContextUnit(modifiedContainer.GetRootContainer().GetName());
		if (modifiedContainer instanceof StorageUnit) {
			view.setContextSupply("");
			view.setContextGroup("");
		}
		else {
			
			view.setContextSupply(((ProductGroup)modifiedContainer).
					GetThreeMonth().toString());
			view.setContextGroup(modifiedContainer.GetName());
		}
	}
	
	/**
	 * Find the ProductContainerData parent of this ProductContainer
	 * @param container the ProductContainer who's parent to look for
	 * */
	private ProductContainerData FindParent(ProductContainer container) {
		return FindParent(data_root, container);
	}
	
	/**
	 * Find the ProductContainerData parent of the specified ProductContainer by starting
	 * at node and recursively searching down the tree
	 * @param node the ProductContainerData to search for the parent within
	 * @param container the ProductContainer who's parent to look for
	 * */
	private ProductContainerData FindParent(ProductContainerData node, ProductContainer container) {
		
		if (node.getTag() == container.GetParentContainer())
			return node;
		else {
			ProductContainerData result = null;
			for (int i = 0; i < node.getChildCount(); i++) {
				result = FindParent(node.getChild(i), container);
				
				if (result != null)
					break;
			}
			
			return result;
		}
	}
}
